﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using CivilRegistry.ControlledModel.Extensions;
using CivilRegistry.ControlledModel.Repository;

namespace CivilRegistry.ControlledModel
{
    /// <summary>
    /// The generalized user class.
    /// </summary>
    public abstract partial class User
    {

        #region LocalFields

        /// <summary>
        /// Holds the <see cref="UserRepository"/> instance.
        /// </summary>
        protected static UserRepository repository = new UserRepository();

        /// <summary>
        /// Holds the profile image stream.
        /// </summary>
        private Stream profileImageStream = null;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructs the user locally.
        /// </summary>
        protected User() { }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the age of the <see cref="User"/>.
        /// </summary>
        public int Age
        {
            get { return DateTime.Now.Year - this.DateOfBirth.Year; }
        }

        #endregion

        #region Methods

        #region CRUD Methods

        #region Finder Methods

        /// <summary>
        /// Finds all <see cref="User"/>s.
        /// </summary>
        /// <returns>Found <see cref="User"/> objects, empty if none are found.</returns>
        public static List<User> FindAll()
        {
            return FindAll(user => true);
        }

        /// <summary>
        /// Finds all <see cref="User"/>s by passed predicate.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns>Found <see cref="User"/> objects, empty if none are found.</returns>
        public static List<User> FindAll(Func<User, bool> predicate)
        {
            return repository.FindAll(predicate);
        }

        /// <summary>
        /// Gets the <see cref="User"/> by username and password.
        /// </summary>
        /// <param name="userName">The user name.</param>
        /// <param name="password">The password.</param>
        /// <returns>Found <see cref="User"/> object, null if not found.</returns>
        public static User GeUser(string userName, string password)
        {
            return repository.FindBy(userName, password);
        }

        #endregion

        /// <summary>
        /// To insert the <see cref="User"/>.
        /// </summary>
        /// <param name="user">The <see cref="User"/> to insert.</param>
        /// <returns>The inserted <see cref="User"/> object.</returns>
        internal User Insert(User user)
        {
            User.Validate(user);
            user.AddedDate = DateTime.Now;
            user.AddedUser = this;
            return repository.Insert(user);
        }

        /// <summary>
        /// To update the <see cref="User"/>.
        /// </summary>
        /// <param name="user">The <see cref="User"/> to update.</param>
        /// <returns>The updated <see cref="User"/> object.</returns>
        internal User Update(User user)
        {
            User.Validate(user, false);
            user.ModifiedDate = DateTime.Now;
            user.ModifiedUser = this;
            return repository.Update(user);
        }

        /// <summary>
        /// To delete the <see cref="User"/>.
        /// </summary>
        /// <param name="user">The <see cref="User"/> to delete.</param>
        internal void Delete(User user)
        {
            user.ModifiedDate = DateTime.Now;
            user.ModifiedUser = this;
            repository.Delete(user);
        }

        /// <summary>
        /// To save the changes made to this object.
        /// </summary>
        /// <returns>Saved <see cref="User"/> object.</returns>
        public User SaveChanges()
        {
            if (this.profileImageStream != null)
            {
                FileHandler.SetProfileImage(this.UserId, profileImageStream);
            }

            this.ModifiedDate = DateTime.Now;
            this.ModifiedUser = this;
            return repository.Update(this);
        }

        /// <summary>
        /// To change the password.
        /// </summary>
        /// <param name="newPassword">The new password.</param>
        public void ChangePassword(string newPassword)
        {
            newPassword.Required("newPassword");
            this.Password = newPassword;
            this.IsFirstLogin = false;
            this.SaveChanges();
        }

        /// <summary>
        /// To activate the <see cref="User"/>.
        /// </summary>
        /// <param name="user">The <see cref="User"/> to activate.</param>
        public void ActivateUser(User user)
        {
            user.DeactivateReason = null;
            user.IsFirstLogin = true;
            user.ModifiedUser = this;
            user.ModifiedDate = DateTime.Now;
            user.SaveChanges();
        }

        /// <summary>
        /// To unsubscribe by <see cref="UnsubscriptionTypeEnum"/>.
        /// </summary>
        /// <param name="unsbscriptionType">The <see cref="UnsubscriptionTypeEnum"/>.</param>
        /// <returns>The <see cref="Unsubscription"/> object. </returns>
        public Unsubscription Unsubscribe(UnsubscriptionTypeEnum unsbscriptionType)
        {
            Unsubscription unsubscription = new Unsubscription();
            unsubscription.UnsubscribedUser = this;
            unsubscription.UnsubscriptionType = UnsubscriptionType.FindById((int)unsbscriptionType);
            return Unsubscription.Insert(unsubscription);
        }

        #endregion

        #region Validation

        /// <summary>
        /// Validates the <see cref="User"/>.
        /// </summary>
        /// <param name="user">The <see cref="User"/> to validate.</param>
        /// <param name="insert">The insertion flag.</param>
        /// <exception cref="ArgumentNullException">If the required fields are null or having default values.</exception>
        internal static void Validate(User user, bool insert = true)
        {
            user.UserName.Required("UserName");

            if (insert && IsUserNameExists(user.UserName))
                throw new ApplicationException("The UserName " + user.UserName + " already exists. Please choos a different name.");

            user.FirstName.Required("FirstName");
            user.MiddleName.Required("MiddleName");
            user.EmailAddress.Required("EmailAddress");
            user.PhoneNo.Required("PhoneNo");
            user.DateOfBirth.Required("DateOfBirth");
            user.PermanentAddress.Required("PermanentAddress");

            PermanentAddress.Validate(user.PermanentAddress);

            if (user.PresentAddress != null)
            {
                PresentAddress.Validate(user.PresentAddress);
            }
        }

        /// <summary>
        /// Checks whether the UserName exists.
        /// </summary>
        /// <param name="userName">The UserName to check.</param>
        /// <returns>True if exists, else false.</returns>
        public static bool IsUserNameExists(string userName)
        {
            if (repository.FindBy(user => user.UserName == userName) != null)
                return true;
            else
                return false;
        }

        #endregion

        #region Path Retrival Methods

        /// <summary>
        /// To retrive the profile image path.
        /// </summary>
        /// <returns>Profile image path.</returns>
        public string GetProfileImagePath()
        {
            return FileHandler.GetProfileImagePath(this.UserId);
        }

        /// <summary>
        /// To retrive the profile image.
        /// </summary>
        /// <returns>The profile image.</returns>
        public Image GetProfileImage()
        {
            return FileHandler.GetProfileImage(this.UserId);
        }

        /// <summary>
        /// To set the profile image.
        /// </summary>
        /// <param name="profileImageStream">The profile image stream.</param>
        public void SetProfileImage(Stream profileImageStream)
        {
            this.profileImageStream = profileImageStream;
        }

        /// <summary>
        /// To retrive the <see cref="RationCard"/> xerox file path.
        /// </summary>
        /// <returns>The <see cref="RationCard"/> xerox file path.</returns>
        public string GetRationCardXeroxFilePath()
        {
            return FileHandler.GetRationCardXeroxFilePath(this.UserId);
        }

        /// <summary>
        /// To retrive the <see cref="PermanentAddress"/> electrical bill file path.
        /// </summary>
        /// <returns>The <see cref="PermanentAddress"/> electrical bill file path.</returns>
        public string GetPermanentAddressElectricalBillFilePath()
        {
            return FileHandler.GetPermanentAddressElectricalBillFilePath(this.UserId);
        }

        /// <summary>
        /// To retrive the <see cref="PresentAddress"/> electrical bill file path.
        /// </summary>
        /// <returns>The <see cref="PresentAddress"/> electrical bill file path.</returns>
        public string GetPresentAddressElectricalBillFilePath()
        {
            return FileHandler.GetPresentAddressElectricalBillFilePath(this.UserId);
        }

        /// <summary>
        /// To retrive the pan card xerox file path.
        /// </summary>
        /// <returns>The pan card xerox file path.</returns>
        public string GetPanCardXeroxFilePath()
        {
            return FileHandler.GetPanCardXeroxFilePath(this.UserId);
        }

        #endregion

        #endregion

    }
}